goog.require('dkpoints.tests.mocks');
goog.require('dkpoints.tests.stubFn');
goog.require('dkpoints.ui.RaidMemberList');
goog.require('dkpoints.utils');

goog.require('goog.events');
goog.require('goog.string');
goog.require('goog.testing.ContinuationTestCase');
goog.require('goog.testing.events');


goog.require('goog.testing.jsunit');


var memberList, raiders, bodyContent, stubFn,
    // References to actual objects/methods that will be mocked out.
    listPrototype, realAddRaider, realRemoveRaider, realNameError;
function setUpPage() {
  stubFn = dkpoints.tests.stubFn;
  bodyContent = document.getElementById('body_content');

  listPrototype = dkpoints.ui.RaidMemberList.prototype;
  realAddRaider = listPrototype.addRaider_;
  realRemoveRaider = listPrototype.removeRaider_;
  realNameError = listPrototype.displayNameError_;
}


var listenersAtSetUp;
function setUp() {
  raiders = dkpoints.tests.mocks.getRaiderArray();
  listenersAtSetUp = goog.events.getTotalListenerCount();
}


/**
 * Gets a new RaidMemberList.
 * @param {boolean} editable Defaults to false.
 * @param {Array.<dkpoints.models.Raider>=} newRaiders Custom raiders array.
 * @param {goog.dom.DomHelper=} dom Optional DomHelper.
 * @return {dkpoints.ui.RaidMemberList} A new RaidMemberList.
 */
function getNewMemberList(editable, newRaiders, dom) {
  return new dkpoints.ui.RaidMemberList(newRaiders || raiders,
                                        editable,
                                        goog.dom.getDomHelper(bodyContent));
}


/**
 * Creates a new RaidMemberList, sets it to the global memberList, and renders
 * it into bodyContent.
 * @param {boolean} editable Defaults to false.
 * @param {Array.<dkpoints.models.Raider>=} newRaiders Custom raiders array.
 * @param {goog.dom.DomHelper=} dom Optional DomHelper.
 */
function renderNewMemberList(editable, newRaiders, dom) {
  memberList = getNewMemberList(editable, newRaiders, dom);
  memberList.render(bodyContent);
}


/**
 * Test that the constructor loads the required CSS file.
 */
var testConstructorLoadsRequiredCSS = function() {
  var realInstallCSS = dkpoints.utils.installCSS,
      installedCSSFile = '';
  dkpoints.utils.installCSS = function(stylesheet) {
    installedCSSFile = stylesheet;
    realInstallCSS(stylesheet, true /* cache busting */);
  };
  dkpoints.utils.installCSS.loadedCSS_ = realInstallCSS.loadedCSS_;

  memberList = getNewMemberList(false);
  assertEquals('The constructor should have called installCSS.',
               'raid', installedCSSFile);

  dkpoints.utils.installCSS = realInstallCSS;
};


/**
 * Test that the constructor properly stores constructor args.
 */
var testConstructorParamsAreStoredCorrectly = function() {
  memberList = getNewMemberList(false);
  assertEquals('The component\'s raiders array should be the one we gave it.',
               raiders, memberList.raiders_);
  assertFalse('The component should not be editable.', memberList.editable_);
};


/**
 * Test that, on rendering, the root node is assigned the same ID as the
 * component.  This is very important for later DOM interactions.
 */
var testRootNodeGetsComponentID = function() {
  renderNewMemberList(false);

  assertEquals('The root node of the component should have gotten the ' +
               'component\'s ID.', memberList.getId(),
               bodyContent.firstChild.id);
};


/**
 * Test that the root DOM node of the component has both class names when
 * rendering an editable member list:
 * - raid_members_wrapper
 * - raid_members_wrapper_wide
 */
var testRootNodeCssClassOnRenderEditable = function() {
  var baseClass = 'raid_members_wrapper',
      editableClass = 'raid_members_wrapper_wide';

  renderNewMemberList(true);

  assertEquals('The root node of the component should be bodyContent\'s ' +
               'first child.', bodyContent.firstChild, memberList.getElement());

  var classNames = bodyContent.firstChild.className;

  assertTrue('The root node of the component should have CSS class ' +
             baseClass, classNames.indexOf(baseClass) > -1);
  assertTrue('The root node of the component should have CSS class ' +
             editableClass + ' when editable.',
             classNames.indexOf(editableClass) > -1);
};


/**
 * Test that the root DOM node of the component has the base CSS class but NOT
 * the wide CSS class used for editable lists when rendering a non-editable
 * component.
 */
var testRootNodeCssClassOnRenderNonEditable = function() {
  var baseClass = 'raid_members_wrapper',
      editableClass = 'raid_members_wrapper_wide';

  renderNewMemberList(false);

  assertEquals('The root node of the component should be bodyContent\'s ' +
               'first child.', bodyContent.firstChild, memberList.getElement());

  var classNames = bodyContent.firstChild.className;

  assertTrue('The root node of the component should have CSS class ' +
             baseClass, classNames.indexOf(baseClass) > -1);
  assertTrue('The root node of the component should not have CSS class ' +
             editableClass + ' when non-editable.',
             classNames.indexOf(editableClass) === -1);
};


/**
 * Test that creating a non-editable component does not include editing
 * controls.
 */
var testNonEditableMemberListDoesNotHavEditingControls = function() {
  renderNewMemberList(false);

  var buttonClass = 'goog-imageless-button',
      buttons = goog.dom.getElementsByClass(buttonClass, bodyContent),
      inputBoxes = goog.dom.getElementsByTagNameAndClass('input',
                                                         undefined,
                                                         bodyContent);

  assertEquals('There shoud not be any buttons rendered.',
               0, buttons.length);
  assertEquals('There should not be a new raider name input.',
               0, inputBoxes.length);
  assertEquals('No event listeners should have been registered.',
               listenersAtSetUp, goog.events.getTotalListenerCount());
};


/**
 * Test that creating an editable component renders the editing controls.
 */
var testEditableMemberListDoesHavEditingControls = function() {
  renderNewMemberList(true);

  var buttonClass = 'goog-imageless-button',
      buttons = goog.dom.getElementsByClass(buttonClass, bodyContent),
      inputBoxes = goog.dom.getElementsByTagNameAndClass('input',
                                                         undefined,
                                                         bodyContent),
      expected = raiders.length + 1;

  assertEquals('There shoud be ' + expected + ' buttons rendered.',
               expected, buttons.length);
  assertEquals('There should be a new raider name input.',
               1, inputBoxes.length);
  assertNotEquals('Event listeners should have been registered.',
                  listenersAtSetUp, goog.events.getTotalListenerCount());
};


/**
 * Test that the raider names in the DOM correspond to the names passed
 * to the component.
 */
var testRaiderNamesCorrespondToGivenRaiderArray = function() {
  renderNewMemberList(false);

  var root = bodyContent.firstChild,
      playernameEls = goog.dom.getElementsByClass('playername', root),
      expected = raiders.length;

  assertEquals('There should be ' + expected + ' playername nodes.',
               expected, playernameEls.length);

  for (var i = 0; i < expected; ++i) {
    var raiderEl = playernameEls[i],
        name = goog.dom.getTextContent(raiderEl.children[0]),
        className = playernameEls[i].className,
        expectedName = raiders[i].getName(),
        expectedClass = dkpoints.models.Raider.CSSClass[raiders[i].getClass()];

    assertEquals('Player in position ' + i + ' should be ' + expectedName,
                 name, expectedName);
    assertTrue('playername node ' + i + ' should have class ' + expectedClass,
               className.indexOf(expectedClass) !== -1);
  }
};


/**
 * Test that a goog.ui.Button is instantiated for every raider name.
 */
var testButtonsInstantiatedFoEveryRaider = function() {
  renderNewMemberList(true);

  var buttons = goog.dom.getElementsByClass('goog-imageless-button',
                                            memberList.getElement()),
      expectedBtnCount = raiders.length + 1; // Add raider button.

  assertEquals('Button count on the page should be the number of raiders + 1.',
               expectedBtnCount, buttons.length);
  assertEquals('Button count should match memberList.buttons_.length.',
               expectedBtnCount, memberList.buttons_.length);

  for (var i = 0, len = raiders.length; i < len; ++i) {
    var raider = raiders[i].name_,
        buttonId = memberList.buttons_[i].getId();

    assertTrue('Button ID should include the player\'s name.',
               buttonId.indexOf(raider) > -1);
  }

  assertTrue('Last button should be the "addBtn" button.',
             memberList.buttons_[i].getId().indexOf('addBtn') > -1);
};


/**
 * Test that click events on the buttons trigger the component's buttonAction_
 * method.
 */
var testClickEventsOnButtonsTriggerHandler = function() {
  var realHandler = listPrototype.buttonAction_,
      e = null,
      called = false;

  // Can't use a new stub for every run because this gets bound at render-time.
  listPrototype.buttonAction_ = function(event) {
    e = event;
    called = true;
  };

  renderNewMemberList(true);

  var buttons = memberList.buttons_,
      Event = goog.events.Event,
      type = goog.ui.Component.EventType.ACTION;
  for (var i = 0, len = buttons.length; i < len; ++i) {
    var button = buttons[i],
        id = button.getId();
    button.dispatchEvent(new Event(type));

    assertTrue('The buttonAction_ method should have been called for button ' +
               id, called);
    assertEquals('The target given to buttonAction_ should be the ' +
                 'goog.ui.Button w/ id ' + id, button, e.target);

    e = null;
    called = false;
  }

  listPrototype.buttonAction_ = realHandler;
};


/**
 * Test buttonAction_ for a valid Remove Raider button click.
 */
var testButtonActionOnRemoveButtonClick = function() {
  renderNewMemberList(true);

  var type = goog.ui.Component.EventType.ACTION;
  for (var i = 0, len = raiders.length; i < len; ++i) {
    var button = memberList.buttons_[0],
        raider = raiders[0],
        event = new goog.events.Event(type, button);

    var add = listPrototype.addRaider_ = stubFn(); // Shouldn't get called.
    var rmv = listPrototype.removeRaider_ = stubFn(); // Should get called.

    memberList.buttonAction_(event);

    assertFalse('addRaider_ should not have been called.', add.called);
    assertEquals('removeRaider_ should have been given the raider name ' +
                 raider.name_, raider.name_, rmv.args[0]);
    assertEquals('removeRaider_ should have been given the button ' +
                 button.getId(), button, rmv.args[1]);
  }
};


/**
 * Test that when called with a valid name, buttonAction_:
 * - calls Raider.isValidName before calling addRaider_
 * - calls addRaider_ with the given name
 * - clears the New Raider name input
 * - sets focus to the New Raider name input element.
 */
var testButtonActionWithValidName = function() {
  renderNewMemberList(true);

  var rmv = listPrototype.removeRaider_ = stubFn(),
      err = listPrototype.displayNameError = stubFn(),
      realIsValid = dkpoints.models.Raider.isValidName,
      listID = memberList.getId(),
      newNameInput = goog.dom.getElement(listID + '.new_raider'),
      name = 'Russ',
      buttons = memberList.buttons_,
      button = buttons[buttons.length - 1],
      event = {
        target: button
      };

  var isValid = dkpoints.models.Raider.isValidName = function(name) {
    isValid.called = true;
    isValid.args = arguments;
    assertFalse('addRaider_ called before isValidName.', add.called);
    return true;
  };
  isValid.called = false;

  var add = listPrototype.addRaider_ = function(name) {
    add.called = true;
    add.args = arguments;
    assertTrue('isValidName not called before addRaider_.', isValid.called);
  };
  add.called = false;

  newNameInput.value = name;
  memberList.buttonAction_(event);

  assertFalse('removeRaider_ should not have been called.', rmv.called);
  assertFalse('displayNameError_ should not have been called.', err.called);

  assertEquals('isValidName not called with the name.', name, isValid.args[0]);
  assertEquals('addRaider_ not called with the name.', name, add.args[0]);

  assertEquals('New Raider input box should have been cleared.',
               '', newNameInput.value);
  assertEquals('New Raider input box should have beenf focused.',
               newNameInput, document.activeElement);

  dkpoints.models.Raider.isValidName = realIsValid;
};


/**
 * Test that when called with an invalid name, buttonAction_:
 * - calls Raider.isValidName
 * - does not call addRaider_
 * - does not call removeRaider_
 * - calls displayNameError_ with an appropriate message
 * - does not change the New Raider name input's contents
 * - sets focus to the New Raider name input element
 */
var testButtonActionOnAddRaiderButtonClickWithInvalidName = function() {
  renderNewMemberList(true);

  var type = goog.ui.Component.EventType.ACTION,
      buttons = memberList.buttons_,
      lastButton = buttons.length - 1,
      button = memberList.buttons_[lastButton],
      listID = memberList.getId(),
      event = new goog.events.Event(type, button),
      newName = 'Russ5',
      newNameInput = goog.dom.getElement(listID + '.new_raider'),
      message = '"' + newName + '"' + ' is not a valid character name.';

  newNameInput.value = newName;
  newNameInput.focus();

  var add = listPrototype.addRaider_ = stubFn(); // Shouldn't get called.
  var rmv = listPrototype.removeRaider_ = stubFn(); // Shouldn't get called.
  var err = listPrototype.displayNameError_ = stubFn(); // Should get called.
  var realIsValid = dkpoints.models.Raider.isValidName;
  var isValid = dkpoints.models.Raider.isValidName = stubFn(false);

  memberList.buttonAction_(event);

  assertTrue('isValidName should have been called.', isValid.called);
  assertFalse('removeRaider_ should not have been called.', rmv.called);
  assertFalse('addRaider_ should not have been called.', add.called);
  assertEquals('displayNameError_ should have been called with the message: ' +
               message, message, err.args[0]);

  assertEquals('The New Raider input should have focus.',
               newNameInput, document.activeElement);
  assertEquals('The New Raider input value should not have been changed.',
               newName, newNameInput.value);

  dkpoints.models.Raider.isValidName = realIsValid;
};


/**
 * Test that firing a keypress on the New Raider input box triggers the
 * handleKeyPress_ method.
 */
var testHandleKeypressTriggeredOnNewRaiderInputKeyPress = function() {
  var realHandleKeyPress = listPrototype.handleKeyPress_,
      handler = listPrototype.handleKeyPress_ = stubFn();

  renderNewMemberList(true);

  var listID = memberList.getId(),
      newNameInput = goog.dom.getElement(listID + '.new_raider');

  goog.testing.events.fireKeySequence(newNameInput, goog.events.KeyCodes.SPACE);
  assertTrue('Handler should have been called.', handler.called);

  listPrototype.handleKeyPress_ = realHandleKeyPress;
};


/**
 * Test that handleKeyPress does nothing on Enter keypress when the name is
 * blank.
 */
var testHandleKeyPressEnterDoesNothingWithNoName = function() {
  renderNewMemberList(true);

  var add = listPrototype.addRaider_ = stubFn(),
      rmv = listPrototype.removeRaider_ = stubFn(),
      err = listPrototype.displayNameError = stubFn();

  var listID = memberList.getId(),
      newNameInput = goog.dom.getElement(listID + '.new_raider');

  memberList.handleKeyPress_({target: newNameInput});

  assertFalse('addRaider_ should not have been called with no name.',
              add.called);
  assertFalse('removeRaider_ should not have been called with no name.',
              rmv.called);
  assertFalse('displayNameError should not have been called with no name.',
              add.called);
};


/**
 * Test that on an Enter keypress with a valid name, handleKeyError_:
 * - calls Raider.isValidName before calling addRaider_
 * - calls addRaider_ with the given name
 * - clears the New Raider name input
 * - sets focus to the New Raider name input element.
 */
var testHandlekeyPressEnterWithValidName = function() {
  renderNewMemberList(true);

  var rmv = listPrototype.removeRaider_ = stubFn(),
      err = listPrototype.displayNameError = stubFn(),
      realIsValid = dkpoints.models.Raider.isValidName,
      listID = memberList.getId(),
      newNameInput = goog.dom.getElement(listID + '.new_raider'),
      name = 'Russ',
      event = {
        target: newNameInput,
        keyCode: goog.events.KeyCodes.ENTER
      };

  var isValid = dkpoints.models.Raider.isValidName = function(name) {
    isValid.called = true;
    isValid.args = arguments;
    assertFalse('addRaider_ called before isValidName.', add.called);
    return true;
  };
  isValid.called = false;

  var add = listPrototype.addRaider_ = function(name) {
    add.called = true;
    add.args = arguments;
    assertTrue('isValidName not called before addRaider_.', isValid.called);
  };
  add.called = false;

  newNameInput.value = name;
  memberList.handleKeyPress_(event);

  assertFalse('displayNameError_ should not have been called.', err.called);
  assertFalse('removeRaider_ should not have been called.', rmv.called);

  assertEquals('isValidName not called with the name.', name, isValid.args[0]);
  assertEquals('addRaider_ not called with the name.', name, add.args[0]);

  assertEquals('New Raider input box should have been cleared.',
               '', newNameInput.value);
  assertEquals('New Raider input box should have beenf focused.',
               newNameInput, document.activeElement);

  dkpoints.models.Raider.isValidName = realIsValid;
};


/**
 * Test that handleKeyPress does not call addRaider_ on an invalid name.
 */
var testHandleKeyPressEnterWithInvalidName = function() {
  renderNewMemberList(true);

  var isValid = dkpoints.models.Raider.isValidName = stubFn(false),
      add = listPrototype.addRaider_ = stubFn(),
      rmv = listPrototype.removeRaider_ = stubFn(),
      err = listPrototype.displayNameError_ = stubFn(),
      realIsValid = dkpoints.models.Raider.isValidName,
      listID = memberList.getId(),
      newNameInput = goog.dom.getElement(listID + '.new_raider'),
      name = 'Russ 5',
      expectedMessage = '"' + name + '"' + ' is not a valid character name.',
      event = {
        target: newNameInput,
        keyCode: goog.events.KeyCodes.ENTER,
        preventDefault: stubFn()
      };

  newNameInput.value = name;
  memberList.handleKeyPress_(event);

  assertEquals('isValidName should have been called with the given name.',
               name, isValid.args[0]);
  assertFalse('addRaider_ shoud not have been called.', add.called);
  assertTrue('event.preventDefault should have been called .',
             event.preventDefault.called);
  assertEquals('displayNameError not called with the proper message.',
               expectedMessage, err.args[0]);

  dkpoints.models.Raider.isValidName = realIsValid;
};


/**
 * Test that when handleKeyPress is called for a SPACE keypress, it shows an
 * error message.
 */
var testHandleKeyPressSpaceShowsErrorMessage = function() {
  renderNewMemberList(true);

  var err = listPrototype.displayNameError_ = stubFn(),
      listID = memberList.getId(),
      newNameInput = goog.dom.getElement(listID + '.new_raider'),
      expectedMessage = 'Character names cannot include spaces.',
      event = {
        target: newNameInput,
        keyCode: goog.events.KeyCodes.SPACE,
        preventDefault: stubFn()
      };

  newNameInput.value = name;
  memberList.handleKeyPress_(event);

  assertTrue('event.preventDefault should have been called .',
             event.preventDefault.called);
  assertEquals('displayNameError not called with the proper message.',
               expectedMessage, err.args[0]);
};


/**
 * Test that, on its first calling, displayNameError_:
 * - creats a pop-up
 * - sets HideOnEscape to true
 * - sets the message to the given message
 * - makes the pop-up visible.
 */
var testDisplayNameErrorInitializesAndDisplaysPopupOnFirstError = function() {
  renderNewMemberList(true);

  var message = 'Test message.';
  memberList.displayNameError_(message);

  var popup = memberList.newRaiderPopup_,
      el = popup.getElement(),
      className = 'invalid_name_popup';

  assertTrue('newRaiderPopup_ should be a popup.',
             popup instanceof goog.ui.Popup);
  assertTrue('newRaiderPopup_ element should have class ' + className,
             goog.dom.classes.has(el, className));
  assertEquals('newRaiderPopup_ element\'s contents do not match the message.',
               message, goog.dom.getTextContent(el));
  assertTrue('The popup should be visible.', popup.isVisible());
  assertTrue('The popup should be hideable by hitting Escape.',
             popup.getHideOnEscape());
};


/**
 * Test that, on subsequent calls, displayNameError_:
 * - reuses the first pop-up
 * - sets the subsequent messages correctly
 * - makes the pop-up visible
 */
var testDisplayNameErrorReusesPopupOnSubsequentErrors = function() {
  renderNewMemberList(true);

  var message1 = 'Initial error message.',
      message2 = 'Second error message.';
  memberList.displayNameError_(message1);

  var popup = memberList.newRaiderPopup_,
      el = popup.getElement();

  popup.setVisible(false);

  memberList.displayNameError_(message2);
  assertEquals('memberList should have re-used the same popup.',
               popup, memberList.newRaiderPopup_);
  assertEquals('popup should have re-used the same element.',
               el, popup.getElement());
  assertEquals('Popup should have set the new message.',
               message2, goog.dom.getTextContent(el));
  assertTrue('The popup should be visible.', popup.isVisible());
};


/**
 * Test that addRaider_ calls displayNameError and returns when given a name
 * for someone already in the raid.
 */
var testAddRaiderDisplaysErrorWhenGivenNameAlreadyInRaid = function() {
  renderNewMemberList(true);

  var name = raiders[0].getName(),
      err = listPrototype.displayNameError_ = stubFn(),
      members = goog.dom.getElementsByClass('raid_member'),
      initialCount = members.length;

  memberList.addRaider_(name);

  assertTrue('displayNameError_ should have been called.', err.called);
  assertEquals('No new name should have been added.',
               initialCount, goog.dom.getElementsByClass('raid_member').length);
};


/**
 * Test that addRaider generates a well-formed DOM structure and appends it to
 * the right portion of the component's DOM.
 */
var testAddRaiderGeneratesProperDomStructure = function() {
  renderNewMemberList(true);

  var name = 'Notinraid',
      initialCount = goog.dom.getElementsByClass('raid_member').length;

  memberList.addRaider_(name);

  var members = goog.dom.getElementsByClass('raid_member'),
      count = members.length;
  assertEquals('New "raid_member" div should have been added to the DOM.',
               initialCount + 1, count);

  var i = count - 1,
      newMember = members[count - 2], // New member goes before the <input>
      label = newMember.firstChild; // playername div.

  assertEquals('First child of the new raider label should be a text node.',
               goog.dom.NodeType.TEXT, label.firstChild.nodeType);
  assertEquals('The player label should start with an index number.',
               i + '. ', goog.dom.getTextContent(label.firstChild));
  assertEquals('Second child of the new raider label should be a link.',
               goog.dom.TagName.A, label.children[0].nodeName);
  assertEquals('Raider link should contain the raider name.',
               name, goog.dom.getTextContent(label.children[0]));
};


/**
 * Test that addRaider properly creates a new Button for the removal of the new
 * raider.
 */
var testAddRaiderCreatesNewRemoveButton = function() {
  renderNewMemberList(true);

  var name = 'Notinraid',
      buttons = memberList.buttons_,
      initialCount = buttons.length;

  memberList.addRaider_(name);

  assertEquals('addRaider_ should have placed a new button in buttons_ array.',
               initialCount + 1, buttons.length);

  var newButton = buttons[initialCount - 1],
      id = newButton.getId(),
      expectedId = memberList.getId() + '.rmv' + name;

  assertEquals('The new button\'s ID should be ' + expectedId, expectedId, id);
};


/**
 * Test that addRaider properly appends the new raider to the end of the
 * raiders_ array.
 */
var testAddRaiderAddsRaiderToRaiderArray = function() {
  renderNewMemberList(true);

  var name = 'Notinraid',
      raiderList = memberList.raiders_,
      initialCount = raiderList.length;

  memberList.addRaider_(name);

  assertEquals('addRaider_ should have placed a new Raider object in the ' +
               'raiders_ array.', initialCount + 1, raiderList.length);

  var newRaider = raiders[initialCount];
  assertEquals('Added Raider should have had name: ' + name,
               name, newRaider.getName());
  assertEquals('Added Raider should have had class: ' +
               dkpoints.models.Raider.Class.NONE,
               dkpoints.models.Raider.Class.NONE, newRaider.getClass());
};


/**
 * Test that successfully adding a raider fires an ADDED event.
 */
var testAddRaiderSuccessfullyFiresAddEvent = function() {
  renderNewMemberList(true);

  var name = 'Notinraid',
      handler = stubFn();

  var key = goog.events.listen(memberList,
                               dkpoints.ui.RaidMemberList.EventType.ADDED,
                               handler);

  memberList.addRaider_(name);
  assertTrue('Listener should have been notified.', handler.called);
  assertEquals('Listener should have been given the added name.',
               name, handler.args[0].name);
  assertEquals('Listener was given the wrong type of event.',
               dkpoints.ui.RaidMemberList.EventType.ADDED,
               handler.args[0].type);

  goog.events.unlistenByKey(key);
};


/**
 * Test that removeRaider_ throws an error if it receives a name that is not
 * in the raid.
 */
var testRemoveRaiderThrowsIfGivenBadNameAndDoesntWithGood = function() {
  renderNewMemberList(true);

  var name = 'Notinraid',
      shouldThrow = function() {
        memberList.removeRaider_(name);
      },
      expectedError = Error(name + ' is not in the raid.'),
      receivedError = assertThrows('Calling removeRaider_ with a name that ' +
                                   'is not in the list should throw an error.',
                                   shouldThrow);

  assertEquals('Did not receive the expected error message.',
               expectedError.message, receivedError.message);

  var shouldNotThrow = function() {
    var name = memberList.raiders_[0].getName(),
        button = memberList.buttons_[0];
    memberList.removeRaider_(name, button);
  };
  assertNotThrows('Should not throw when the name is in the raid.',
                  shouldNotThrow);
};


/**
 * Test that removeRaider_ removes the given raider from the DOM structure.
 * The test removes someone in the middle.
 */
var testRemoveRaiderRemovesRaiderFromDOM = function() {
  renderNewMemberList(true);

  var raiders = memberList.raiders_,
      buttons = memberList.buttons_,
      i = Math.floor(raiders.length / 2),
      name = raiders[i].getName(),
      isInRaid = false,
      re = new RegExp('^\\d+\\. ' + name + '$'),
      raidMembers = goog.dom.getElementsByClass('playername',
                                                memberList.getElement());

  // Make sure he's in the raid first.  We don't want false positives.
  for (var j = 0, len = raidMembers.length; j < len; ++j) {
    var text = goog.dom.getTextContent(raidMembers[j]);

    if (re.test(text)) {
      isInRaid = true;
      break;
    }
  }

  if (! isInRaid) {
    fail('Could not find "' + name + '" in raid before removing.');
  }

  memberList.removeRaider_(name, buttons[i]);

  isInRaid = false;
  raidMembers = goog.dom.getElementsByClass('playername',
                                            memberList.getElement());
  for (j = 0, len = raidMembers.length; j < len; ++j) {
    text = goog.dom.getTextContent(raidMembers[j]);

    if (re.test(text)) {
      isInRaid = true;
      break;
    }
  }

  assertFalse(name + ' is still in the raid.', isInRaid);
};


/**
 * Test that removeRaider_ properly renumbers the ones left in the group.
 */
var testRemoveRaiderRenumbering = function() {
  renderNewMemberList(true);

  var raiders = memberList.raiders_,
      buttons = memberList.buttons_,
      i = Math.floor(raiders.length / 2),
      name = raiders[i].getName();

  memberList.removeRaider_(name, buttons[i]);

  var raidMembers = goog.dom.getElementsByClass('playername',
                                                memberList.getElement());

  // Last playername div is the New Raider input box.  Skip it.
  for (var i = 0, len = raidMembers.length - 1; i < len; ++i) {
    var text = goog.dom.getTextContent(raidMembers[i]);
    assertTrue('Renumbering mistake at position ' + i + ': ' + text,
               goog.string.startsWith(text, (i + 1) + '.'));
  }
};


/**
 * Test that removeRaider_ removes the raider from raiders_ and the button from
 * butons_.
 */
var testRemoveRaiderRemovesButtonAndRaiderObject = function() {
  renderNewMemberList(true);

  var raiders = memberList.raiders_,
      buttons = memberList.buttons_,
      i = Math.floor(raiders.length / 2),
      name = raiders[i].getName(),
      raiderCount = raiders.length,
      buttonCount = buttons.length,
      buttonID = buttons[i].getId();

  memberList.removeRaider_(name, buttons[i]);

  assertEquals('removeRaider_ should have decreased the raider count by 1.',
               raiderCount - 1, raiders.length);
  assertEquals('removeRaider_ should have decreased the button count by 1.',
               buttonCount - 1, buttons.length);

  for (var i = 0, len = raiders.length; i < len; ++i) {
    assertNotEquals('Raider should have been removed from raiders_ array.',
                    name, raiders[i].getName());
    assertNotEquals('Button should have been removed from buttons_ array.',
                    buttonID, buttons[i].getId());
  }
};


/**
 * Test that removeRaider_ throws the given name is in the raiders array at
 * raiders_[i], but label div [i] contains a different player.
 */
var testRemoveRaiderThrowsIfLabelDoestMatchRaiderName = function() {
  renderNewMemberList(true);

  var raiders = memberList.raiders_,
      buttons = memberList.buttons_,
      i = Math.floor(raiders.length / 2),
      name = raiders[i].getName(),
      raidMembers = goog.dom.getElementsByClass('playername');

  goog.dom.setTextContent(raidMembers[i].children[0],
                          'Not the droids you\'re looking for.');

  var shouldThrow = function() {
                      memberList.removeRaider_(name, buttons[i]);
                    },
      expectedError = 'Raider list out of sync.  Cound not remove raider: ' +
                      name,
      error = assertThrows('Should have received an error when called.',
                           shouldThrow);

  assertEquals('Did not receive the expected error message.',
               expectedError, error.message);
};


/**
 * Test that successfully removing a raider fires a REMOVED event.
 */
var testRemoveRaiderSuccessfullyFiresRemovedEvent = function() {
  renderNewMemberList(true);

  var raiders = memberList.raiders_,
      i = Math.floor(raiders.length / 2),
      button = memberList.buttons_[i],
      name = raiders[i].getName(),
      handler = stubFn();

  var key = goog.events.listen(memberList,
                               dkpoints.ui.RaidMemberList.EventType.REMOVED,
                               handler);

  memberList.removeRaider_(name, button);
  assertTrue('Listener should have been notified.', handler.called);
  assertEquals('Listener should have been given the added name.',
               name, handler.args[0].name);
  assertEquals('Listener was given the wrong type of event.',
               dkpoints.ui.RaidMemberList.EventType.REMOVED,
               handler.args[0].type);

  goog.events.unlistenByKey(key);
};


function tearDown() {
  listPrototype.addRaider_ = realAddRaider;
  listPrototype.removeRaider_ = realRemoveRaider;
  listPrototype.displayNameError_ = realNameError;

  memberList.dispose();
  memberList = null;

  bodyContent.innerHTML = '';

  // Test for non-disposed listeners after each test.
  assertEquals('Event listeners left over after tear down.',
               listenersAtSetUp, goog.events.getTotalListenerCount());
}


var testCase = new goog.testing.ContinuationTestCase(document.title);
testCase.autoDiscoverTests();
G_testRunner.initialize(testCase);
